import os
import queue
import shutil
import threading
import pandas as pd
import time
import traceback  # 添加在文件顶部
from DrissionPage import Chromium, ChromiumOptions, SessionOptions
from DrissionPage.common import Settings
from email_permission import email_list
from DataRecorder import Recorder
from DrissionPage.errors import ElementNotFoundError
from handle_asset_link import collect_direct_link_list_exists

from read_search_file import get_complete_sheet1, get_search_comp, mock_click_js, get_direct_link_sheet,get_set_note_ids
from create_unit import create_unit
from loguru import logger
from datetime import datetime

lock = threading.Lock()
Settings.set_language("zh_cn")
co = ChromiumOptions(read_file=False)  # 不读取文件方式新建配置对象
so = SessionOptions(read_file=False)
browser = Chromium(addr_or_opts=co, session_options=so)

def log_message(log_callback, message, color=None):
    if callable(log_callback):
        log_callback(message, color) if color else log_callback(message)


def is_number(value):
    return type(value) in (int, float, complex)

# tag:div@@class=flexible@@text()=点击成本控制

def create_plan(plan_view_tab, row_excel, file_path, log_callback=None, df_sheet=None):
    # plan_view_tab.set.scroll.smooth(on_off=False)
    plan_view_tab.wait.load_start()
    plan_view_tab.wait.eles_loaded('tag:input@@class=d-text@@type=text@@placeholder=请输入你的计划名称@@maxlength=50')

    plan = row_excel['计划名']
    ad_type = row_excel['广告类型']
    promotion_target = row_excel['推广目标']
    bidding_type = row_excel['出价方式']
    cost_ctr_type = row_excel['成本控制方式']
    plan_budget = row_excel['指定预算']
    ad_group = row_excel['广告组']
    consume_rate = row_excel['消耗速率']
    trans_comp = row_excel['转化组件']
    asset_event = row_excel['资产事件']

    if trans_comp == '搜索组件':
        data_df = get_search_comp(file_path)
        if data_df.empty:
            log_message(log_callback, f"当前计划名【{plan}】选项是搜索组件的情况下， 表格中缺少搜索组件sheet页", "red")
            return

    plan_view_tab.ele('tag:span@@text()=推广您的线上App或者小红书小程序，吸引用户完成下载或在应用/小程序内行动').click(by_js=True)
    time.sleep(1.5)

    # 输入计划名称
    plan_view_tab.ele('tag:input@@class=d-text@@type=text@@placeholder=请输入你的计划名称@@maxlength=50').input(plan, clear=True)

    # 点击广告类型
    if ad_type_ele := plan_view_tab.ele(f'tag:span@@text()={ad_type}', timeout=2):
        ad_type_ele.click(by_js=True)

    # 点击推广目标
    if promotion_target_ele := plan_view_tab.ele(f'tag:span@@text():{promotion_target}'):
        promotion_target_ele.scroll.to_center()
        promotion_target_ele.click(by_js=True)
        time.sleep(1)

    # 资产事件
    if plan_view_tab.ele('tag:div@@text()=请选择跳转的资产事件', timeout=2):
        asset_event_handle(asset_event, log_callback, plan, plan_view_tab)

    # 出价方式
    if bidding_type_ele := plan_view_tab.ele(f'tag:div@@class=flexible@@text()^{bidding_type}', timeout=3):  # 点击出价方式
        bidding_type_ele.scroll.to_center()
        bidding_type_ele.click(by_js=True)

    # 成本控制方式
    if plan_view_tab.ele(f'tag:div@@class=flexible@@text()={cost_ctr_type}', timeout=3):
        if cost_ctr_type:
            plan_view_tab.ele(f'tag:div@@class=flexible@@text()={cost_ctr_type}').parent(2).run_js('this.click();')

    # 预算类型
    if plan_budget_ele := plan_view_tab.ele(f'tag:span@@text()=指定预算', timeout=2):
        plan_budget_handle(consume_rate, plan_budget, plan_budget_ele, plan_view_tab)

    # 直达链接
    if plan_view_tab.ele(f'tag:span@@text()=直达链接', timeout=2):
        plan_view_tab.ele(f'tag:span@@text()=直达链接').scroll.to_center()
        if '直达链接Deeplink' in df_sheet:
            deep_link = row_excel['直达链接Deeplink']
            if pd.notna(deep_link):
                deep_link = deep_link.strip()
                plan_view_tab.ele(f'tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis@@text()={deep_link}').run_js(mock_click_js)

        if '直达链接UniversalLink' in df_sheet:
            universal_link = row_excel['直达链接UniversalLink']
            if pd.notna(universal_link):
                universal_link = universal_link.strip()
                plan_view_tab.ele(f'tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis@@text()={universal_link}').run_js(mock_click_js)

    # 监测链接
    if monitor_ele := plan_view_tab.ele(f'tag:span@@text()=监测链接信息', timeout=2):
        monitor_ele.scroll.to_center()
        if '监测链接' in df_sheet:
            monitor_link = row_excel['监测链接']
            if pd.notna(monitor_link):
                monitor_link = monitor_link.strip()
                plan_view_tab.ele('tag:input@@class=d-text@@placeholder=请填写监测链接').input(monitor_link, clear=True)

    # 点击广告组
    click_ad_group(ad_group, df_sheet, plan_view_tab, row_excel)

    plan_next_button_ele = plan_view_tab.ele('tag:span@@text()= 下一步 ').parent(2)
    plan_next_button_ele.scroll.to_see()

    if plan_view_tab.ele('@text():计划名称已存在', timeout=1):
        log_message(log_callback, f"计划名称已存在:{plan}", "red")
        return

    # 点击下一步 处理操作频繁
    handle_operate_too_fast(plan_next_button_ele, plan_view_tab, plan, log_callback)

    if plan_view_tab.ele('@text():推广计划名称已存在', timeout=1):
        log_message(log_callback, f"计划名称:【{plan}】 - 已存在", "red")
        return

    print(f"开始进入创建单元页面:unit_view_tab{plan_view_tab.title}")
    create_unit(plan_view_tab, row_excel, browser, file_path, plan_next_button_ele, df_sheet, log_callback)


def asset_event_handle(asset_event, log_callback, plan, plan_view_tab):
    try:
        if '-' not in asset_event:
            log_message(log_callback, f'计划名:{plan} - 资产事件:{asset_event}缺少必要信息，需要用 - 分隔对应选项, 示例:百补会场-APP打开', "red")
            return

        asset_event_arr = asset_event.strip().split('-')
        plan_view_tab.ele('tag:div@@text()=请选择跳转的资产事件').run_js(mock_click_js)

        plan_view_tab.ele(f'tag:span@@class$--color-text-paragraph --size-text-paragraph d-text-nowrap@@text()={asset_event_arr[0].strip()}').run_js('this.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));')
        time.sleep(1)
        plan_view_tab.ele(f'tag:span@@class$--color-text-paragraph --size-text-paragraph d-text-nowrap@@text()={asset_event_arr[1].strip()}').parent(2).run_js(mock_click_js)
        time.sleep(1)
    except ElementNotFoundError as e:
        logger.error(f'计划名:{plan} - 资产事件:{asset_event}没找到对应选项值 - {str(e)}')
        log_message(log_callback, f'计划名:{plan} - 资产事件:{asset_event}没找到对应选项值', "red")


def plan_budget_handle(consume_rate, plan_budget, plan_budget_ele, plan_view_tab):

    if pd.notna(plan_budget):  # 预算类型
        if is_number(plan_budget):  ## 指定预算
            plan_budget_ele.scroll.to_see()
            plan_budget_ele.click(by_js=True)
            time.sleep(1)
            plan_view_tab.ele('tag:input@@class=d-text@@type=number@@placeholder=请输入，默认为最低预算').input(
                plan_budget, clear=True)
            if check_ele := plan_view_tab.ele('tag:span@@class=d-switch-simulator checked --color-bg-primary', timeout=3):  # 取消节假日付算上浮
                check_ele.parent(1).run_js('this.click();')

            # 点击均匀消耗
            if consume_rate == '匀速消耗' or consume_rate == '均匀消耗':
                plan_view_tab.ele(f'tag:span@@text()=均匀消耗').scroll.to_center()
                plan_view_tab.ele(f'tag:span@@text()=均匀消耗').parent(1).click(by_js=True)
            else:
                plan_view_tab.ele(f'tag:span@@text()=加速消耗').scroll.to_center()
                plan_view_tab.ele(f'tag:span@@text()=加速消耗').parent(1).click(by_js=True)


def click_ad_group(ad_group, df_sheet, plan_view_tab, row_excel):
    if '广告组名称' not in df_sheet.columns:
        plan_view_tab.ele(f'tag:span@@text()={ad_group}').parent(1).click(by_js=True)
    else:
        ad_group_name = row_excel['广告组名称']

        if pd.isna(ad_group_name):
            plan_view_tab.ele(f'tag:span@@text()={ad_group}').parent(1).click(by_js=True)
        else:
            plan_view_tab.ele('tag:span@@text()=加入').parent(1).click(by_js=True)
            time.sleep(1)
            if plan_view_tab.ele(f'tag:span@@text()={ad_group_name}', timeout=2):
                plan_view_tab.ele(f'tag:span@@text()={ad_group_name}').parent(4).click(by_js=True)
            elif plan_view_tab.ele(f'tag:span@@text():{ad_group_name}', timeout=2):
                plan_view_tab.ele(f'tag:span@@text():{ad_group_name}').parent(4).click(by_js=True)
            else:
                plan_view_tab.ele(f'tag:span@@text()={ad_group}').parent(1).click(by_js=True)


def handle_operate_too_fast(plan_next_button_ele, plan_view_tab, plan, log_callback):
    max_retries = 10  # 最大重试次数
    retry_count = 0
    while retry_count < max_retries:
        # # 先检查是否已经进入下一页
        # if plan_view_tab.ele('tag:input@@class=d-text@@placeholder=请输入你的单元名称', timeout=2):  # 下一页的标识元素
        #     break
        plan_next_button_ele.click(by_js=True)

        if plan_view_tab.ele('@text():操作频繁', timeout=2):
            log_message(log_callback, f"创建计划页面【{plan}】 - 触发操作频繁,正在重试({retry_count + 1}/{max_retries})", "red")
            logger.info(f"操作频繁，等待后重试 ({retry_count + 1}/{max_retries})")
            time.sleep(2 * (retry_count + 1))
            # try:
            #     plan_next_button_ele.click(by_js=True)
            # except NoRectError:
            #     log_message(log_callback, f"创建计划页面的下一步没有找到:{plan} - 页面已跳转，停止重试", "green")
            #     break
            retry_count += 1
        else:
            break

    if retry_count == max_retries:
        logger.error("已达到最大重试次数，仍然操作频繁")
        log_message(log_callback, f"计划名称: 【{plan}】 - 已达到最大重试次数，仍然操作频繁", "red")
    else:
        log_message(log_callback, f"计划名称【{plan}】-在重试({retry_count + 1}/{max_retries})次后成功执行下一步", "green")
        logger.info("操作成功执行")


'''点击新建计划'''
def insert_plan(row_excel, create_plan_tab, file_path, log_callback, df_sheet):
    plan_ele = create_plan_tab.ele('tag:button@@text():新建计划')

    ignore_pop_ele = create_plan_tab.ele('@text()=暂时忽略', timeout=0.5)
    if ignore_pop_ele:
        ignore_pop_ele.click()
    try:
        lock.acquire()
        plan_view_tab = plan_ele.click.for_new_tab(by_js=True)  # 点击新建计划按钮并打开创建计划表单页面
        plan_view_tab.wait.title_change("新建计划 - 小红书聚光")
    finally:
        lock.release()

    create_plan(plan_view_tab, row_excel, file_path, log_callback, df_sheet)

def worker(task_queue, create_plan_tab, file_path, log_callback, df_sheet):
    """
    工作线程函数，从队列中获取任务并执行自动化操作
    """
    while True:
        row_excel = task_queue.get()
        if row_excel is None:  # 结束信号
            break
        try:
            log_message(log_callback, f"处理中: {row_excel['计划名']}")
            insert_plan(row_excel, create_plan_tab, file_path, log_callback, df_sheet)
        except Exception as e:
            # 获取完整错误信息
            error_msg = f"{type(e).__name__}: {str(e)}"
            error_trace = traceback.format_exc()  # 获取完整堆栈跟踪

            # 前端显示简化错误
            log_message(log_callback, f"执行错误: {row_excel['计划名']} - {error_msg}", "red")

            # 日志记录完整错误
            logger.error(f"错误时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
                         f"计划名: {row_excel['计划名']}\n"
                         f"错误类型: {type(e).__name__}\n"
                         f"错误详情: {error_msg}\n"
                         f"堆栈跟踪:\n{error_trace}")

            r = Recorder(path=f'信息流-{file_path}-出错记录.csv', cache_size=500)
            plan = row_excel['计划名']
            account_id = row_excel['账号ID']
            r.add_data({'出错计划名': plan, '账号ID': account_id})
            r.record()
            continue
        finally:
            task_queue.task_done()
    log_message(log_callback, f"处理完毕", "green")  # 全部处理完毕

def start_sub_account_url(file_path, concurrency_num=2, log_callback=None):
    df_sheet = get_complete_sheet1(file_path)
    missing_ids = get_set_note_ids(df_sheet, file_path=file_path) # 主表中有但是附表中不存在的笔记ID

    if missing_ids:
        log_message(log_callback, f"以下笔记ID在唤端组件页面中缺失: {', '.join(missing_ids)}, 补充必要笔记信息后再次运行", "red")
        return
    if int(concurrency_num) > 5:
        log_message(log_callback, f"并发数量暂时最大为5", "red")
        return
    if '关联定向包' not in df_sheet.columns:
        log_message(log_callback, f"表格中缺少关联定向包列", "red")
        return
    if '封面全选' not in df_sheet.columns:
        log_message(log_callback, f"表格中缺少封面全选列", "red")
        return
    if '资产事件' not in df_sheet.columns:
        log_message(log_callback, f"表格中缺少资产事件列", "red")
        return

    task_queue = queue.Queue()
    # 将 Excel 数据逐行放入队列中
    for index, row in df_sheet.iterrows():
        task_queue.put(row)

    # 打开合作伙伴平台页面
    account_id = df_sheet['账号ID']
    text_account_id = account_id[0].strip()

    account_tab = browser.new_tab('https://partner.xiaohongshu.com/partner/subAccount-list')

    # TODO 加一个校验步骤， 不是叁柒的所属邮箱直接驳回
    account_tab.ele('tag:span@@class=d-text --color-static --color-text-paragraph --size-text-paragraph text label@@text()^登陆邮箱：').next().child().next().click(by_js=True)
    account_tab.wait(0.5)
    email_str = account_tab.ele('tag:span@@class=d-text --color-static --color-text-paragraph --size-text-paragraph text label@@text()^登陆邮箱：').next().child().text
    email_permission_list = email_list(log_callback)
    if email_str not in email_permission_list:
        log_message(log_callback, f"当前邮箱账号:【{email_str}】暂无权限使用", "red")
        return

    account_tab.ele('tag:input@@class=el-input__inner@@type=text@@autocomplete=off@@placeholder=请输入').click()
    account_tab.ele('tag:input@@class=el-input__inner@@type=text@@autocomplete=off@@placeholder=请输入').input(text_account_id)
    account_tab.ele('tag:button@@id=search-btn--1').click(by_js=True)

    account_tab.wait(2)

    search_none_ele = account_tab.ele('tag:tr@@class=d-table__empty-row@@text()=暂无数据', timeout=0.5)
    if search_none_ele:
        log_message(log_callback, f"合作伙伴平台子账号ID:{text_account_id}搜索结果为空, 检查表格第一行子账号ID, 或者检查是否登录合作伙伴平台", "red")
        return

    account_tab.ele('tag:span@@text():跳转', timeout=2).hover()

    plan_tab_arr = []  # 新建计划标签页对象数组
    threads = []
    jg_ele = account_tab.ele('tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis@@text():聚光平台', timeout=2)

    if jg_ele:
        create_plan_tab = jg_ele.click.for_new_tab(by_js=True)
        create_plan_tab.wait.title_change("计划列表")
        unit_dict_cookie = create_plan_tab.cookies(all_info=False).as_dict()
        ## TODO 事先判断一下这些链接在当前账户中存不存在
        direct_sheet = get_direct_link_sheet(file_path) # 准备录入的直达链接
        if not direct_sheet.empty:
            log_message(log_callback, f'存在【直达链接预先录入】sheet页，进行预先录入操作..', "purple")

            direct_link_chunks = [direct_sheet[i:i + 20] for i in range(0, len(direct_sheet), 20)]

            for chunk in direct_link_chunks:
                directLinkList = []
                for dd in chunk.itertuples():
                    link_type = dd.直达链接类型.strip() if pd.notna(dd.直达链接类型) else ''
                    remarkName = dd.直达链接备注名.strip() if pd.notna(dd.直达链接备注名) else ''
                    url = dd.直达链接内容.strip() if pd.notna(dd.直达链接内容) else ''

                    if not link_type or not url:
                        continue  # 跳过 type 或 url 为空的行

                    directLinkList.append({
                        "type": 1 if 'deeplink' in link_type.lower() else 2,
                        "url": url,
                        "remarkName": remarkName
                    })

                data = {"directLinkList": directLinkList}
                collect_direct_link_list_exists(cookies=unit_dict_cookie, account_id=text_account_id, data=data, log_callback=log_callback)
                time.sleep(2)

        time.sleep(1)

        plan_url = create_plan_tab.url
        print(f"点完聚光平台以后跳转的tab:{create_plan_tab.url}")
        plan_tab_arr.append(create_plan_tab)

        if int(concurrency_num) > 1:
            for _ in range(concurrency_num - 1):
                plan_tab_arr.append(browser.new_tab(plan_url))

    for plan_tab in plan_tab_arr:
        logger.info(f"开始创建线程: {threading.current_thread().name} - 给当前线程分配的新建计划tab标签页:{id(plan_tab)}")
        t = threading.Thread(target=worker, args=(task_queue, plan_tab, file_path, log_callback, df_sheet))
        t.start()
        time.sleep(2)
        threads.append(t)

    task_queue.join()

    # 发送结束信号给所有线程
    for _ in range(len(threads)):
        task_queue.put(None)

    # 等待所有线程结束
    for t in threads:
        t.join()

    # 处理完成后清理
    if os.path.exists('temp_images'):
        shutil.rmtree('temp_images')



# if __name__ == '__main__':
#     file_path = r"../shared/唤端-创建计划-样例.xlsx"
#
#     browser = Chromium()  ## 临时测试、到时候删掉， 只保留调用create_unit()函数
#     unit_view_tab = browser.latest_tab  ## 临时测试、到时候删掉
#     unit_view_tab.get('https://ad.xiaohongshu.com/aurora/ad/create/campaign/4?isStep=true&AFormGrayFlag=false&vSellerId=67403ef0c2f45c001519af4b')  ## 临时测试、到时候删掉
#
#     sheet1_data = get_complete_sheet1(file_path)
#     for index, row in sheet1_data.iterrows():
#         if index == 0:
#             create_plan(unit_view_tab, row, file_path, df_sheet=sheet1_data)















